package com.jec.module.business.service;

import java.net.DatagramPacket;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import com.jec.module.business.dao.RecordRepository;
import com.jec.module.business.entity.Record;
import com.jec.module.business.entity.RecordSegment;
import com.jec.module.business.record.RecordSession;
import com.jec.module.business.record.RecordSessionListener;
import com.jec.protocol.pdu.PduConstants;
import com.jec.protocol.processor.RawProcessor;
import com.jec.protocol.unit.BCD;
import com.jec.protocol.unit.BytesWrap;
import com.jec.utils.FileUtil;

import lombok.extern.log4j.Log4j;

/**
 * Created by jeremyliu on 6/18/16.
 */
@Component
@Scope
@Log4j
public class RecordService implements RawProcessor,RecordSessionListener{
    
    @Autowired
    private RecordRepository recordRepository;

    private Map<Integer,RecordSession> sessionMap = new ConcurrentHashMap<>();

    public RecordSession route(int id,String callingNum, String calledNum, BytesWrap data){
        RecordSession session;
        if(sessionMap.containsKey(id))
            session = sessionMap.get(id);
        else {
            session = new RecordSession(id, callingNum, calledNum);
            session.addListener(this);
            session.openSession();
            sessionMap.put(id,session);
            
            log.info("RecordSession created. id=" + id);
        }
        session.addData(data);
        return session;
    }

    public List<Record> getCurrentRecord(){
        List<Record> records = new ArrayList<>();
        for(RecordSession session : sessionMap.values()){
            records.add(session.getRecord());
        }
        return records;
    }

    @Transactional(readOnly = true)
    public List<RecordSegment> getRecordSegment(int id, boolean recording){
        if(recording){
            RecordSession session =  sessionMap.get(id);
            if(session == null)
                return null;
            else
                return session.getRecordSegement();
        }else{
            Record record = recordRepository.findOne(id);
            record.readConfig();
            return record.getSegments();
        }
    }

    @Transactional
    public void addRecord(Record record){
    	recordRepository.save(record);
        //recordDao.save(record);
    }

    @Transactional(readOnly = true)
    public List<Record> getRecord(Date startTime, Date endTime, String key, int offset, int pageSize){
    	
    	//PageRequest pageRequest =  new PageRequest(offset/pageSize, pageSize); 
    	PageRequest pageRequest =  new PageRequest(offset/pageSize, pageSize, Direction.DESC, "startTime"); 

    	Specification<Record> spec = new Specification<Record>(){  
    		@Override  
    		public Predicate toPredicate(Root<Record> root,  
    				CriteriaQuery<?> query, CriteriaBuilder builder) { 

    			List<Predicate> predicates = new ArrayList<>();

    			if(startTime != null) {
    				predicates.add(
    						builder.greaterThanOrEqualTo(root.get("startTime").as(Date.class), startTime)
    						);
    			}

    			if(endTime != null) {
    				predicates.add(
    						builder.lessThanOrEqualTo(root.get("startTime").as(Date.class), endTime)
    						);
    			}

    			if(!StringUtils.isEmpty(key)) {

    				List<Predicate> predicatesOr = new ArrayList<>();

    				predicatesOr.add(
    						builder.like(root.get("callingNumber").as(String.class), "%" + key + "%")
    						); 

    				predicatesOr.add(
    						builder.like(root.get("calledNumber").as(String.class), "%" + key + "%")
    						); 

    				predicates.add(
    						builder.or(predicatesOr.toArray(new Predicate[predicates.size()]))
    						);
    			}

    			return builder.and(predicates.toArray(new Predicate[predicates.size()]));  
    		}  
    	};

    	Page<Record> found = recordRepository.findAll(spec, pageRequest);

    	return found.getContent();
    	/*
        Search search = new Search(Record.class);
        search.addSort("startTime", true);
        if(startTime != null)
            search.addFilterGreaterOrEqual("startTime",startTime);
        if(endTime != null)
            search.addFilterLessOrEqual("endTime",endTime);
        if(key != null && !key.equals("")){
            key = "%" + key + "%";
            Filter callingFilter = new Filter("callingNumber",key,Filter.OP_ILIKE);
            Filter calledFilter = new Filter("calledNumber", key, Filter.OP_ILIKE);
            search.addFilter(Filter.or(callingFilter,calledFilter));
        }
        search.setFirstResult(offset);
        search.setMaxResults(pageSize);
        List<Record> records=  recordDao.search(search);
        return records;
        */
    }

    @Transactional
    public boolean removeRecord(int id){
        Record record = recordRepository.findOne(id);
        if(record != null){
            FileUtil.deleteDir(record.getFilePath());
            recordRepository.delete(record);
            return true;
        }
        return false;
    }

    @Override
    public boolean process(DatagramPacket packet) {

        byte[] data = packet.getData();
        int offset = packet.getOffset();
        int length = packet.getLength();

        BytesWrap bytes = new BytesWrap(data, offset, length);

        if(length == 4) { // 内部控制数据，收到该消息关闭所有Store
            return true;
        }else if(length != 1050) // 录音数据
			return false;

        /*
         * 检查数据头
         */
        if(0x27 != bytes.getInt8(0)) {
            return false;
        }

        if(0x24 != bytes.getInt8(1)) {
            return false;
        }

        if(0x27 != bytes.getInt8(2)) {
            return false;
        }

        if(0x26 != bytes.getInt8(3)) {
            return false;
        }
        int id  = bytes.getInt16(4);

        //System.out.println("Record head: " + HexUtils.toHex(data, 26));

        BCD callingNum = bytes.getBCD(6, PduConstants.LENGTH_OF_BCD);
        BCD calledNum = bytes.getBCD(15, PduConstants.LENGTH_OF_BCD);
        int used  = bytes.getInt16(24);

        //
        if(used > 1024 || used < 0) {
            System.out.println("录音数据长度错误：" + used);
            return false;
        }
        String callingStr = callingNum == null ? "": callingNum.toString();
        String calledStr = calledNum == null ? "": calledNum.toString();
        bytes.setOff(26,used);
        route(id,callingStr,calledStr,bytes);
        return true;
    }

    @Override
    public void onSessionClosed(RecordSession session) {
        int sessionId = session.getSessionId();
        Record record = session.getRecord();
        synchronized(this){
            sessionMap.remove(sessionId);
        }
        if(record.getSegments().size()>0) {
        	recordRepository.save(record);
        	/*
            TransactionSynchronizationManager.initSynchronization();
            recordDao.saveForce(record);*/
        }
    }
    
    public void demo(int id) {
    	
    	String caller = "6101";
    	String called = "6102";
    	
    	for(int i = 0; i < 1024 * 2; i++) {
        	BytesWrap bw = new BytesWrap(new byte[1024]);
        	route(id, caller, called, bw);
    	}

    	
    }
}
